package handler

import (
	"context"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/golang/protobuf/ptypes/wrappers"
	"github.com/jinzhu/gorm"
	"math"
	"sort"
	"time"

	"kop/framework/broker"
	context2 "kop/framework/context"
	"kop/framework/errors"
	"kop/libs/database"
	"kop/modules/item/consts"
	"kop/modules/world_boss/configuration"
	"kop/modules/world_boss/entity"
	"kop/modules/world_boss/models"
	"kop/pb"
	"kop/pb/service"

	awardM "kop/modules/award/models"
	itemC "kop/modules/item/client"
	questC "kop/modules/quest/client"
)

const (
	LittleBossStarHour = 12 // 20波小怪开始时间
	WorldBossStarHour  = 20 // 大Boss开始时间
)

func NewWorldBossServiceHandler(dB *gorm.DB) WorldBossServiceHandler {

	models.AutoMigrate(dB)

	var handler = WorldBossServiceHandler{
		Managers: make(map[int32]*entity.Boss, 1000),
	}

	for i := int32(1); i < 999; i++ {

		var boss = &entity.Boss{
			ServerID: i,
			MaxHP:    configuration.GetBoss().BossHP,
			HP:       configuration.GetBoss().BossHP,
			Damages:  make(map[int32]int64),
		}
		if err := dB.FirstOrCreate(boss).Error; err != nil {
			panic(err)
		}

		go work(dB, boss)
		handler.Managers[i] = boss
	}
	return handler
}

type WorldBossServiceHandler struct {
	context2.Context
	Managers map[int32]*entity.Boss
}

func (h *WorldBossServiceHandler) GetLittleBoss(_ context.Context, req *service.RoleRequest, resp *service.GetLittleBossResp) error {

	const gameTimeHour = 6

	var boss, err = models.GetLittleBoss(database.DB, req.RoleID)
	if err != nil {
		return err
	}

	resp.Level = boss.Level
	resp.Boss = &pb.WorldBoss{
		HP:    boss.HP,
		MaxHP: configuration.GetLittleBoss(boss.Level).BossHP,
		IDs:   boss.IDsMap,
	}

	var now = time.Now()
	if now.Hour() < LittleBossStarHour {

		var year, month, day = now.Date()
		resp.Boss.Second = int32(time.Date(year, month, day, LittleBossStarHour, 0, 0, 0, time.Local).Sub(now).Seconds())
	} else if now.Hour() >= LittleBossStarHour+gameTimeHour {

		var year, month, day = now.AddDate(0, 0, 1).Date()
		resp.Boss.Second = int32(time.Date(year, month, day, LittleBossStarHour, 0, 0, 0, time.Local).Sub(now).Seconds())
	}

	return nil
}

func (h *WorldBossServiceHandler) AttackLittleBoss(ctx context.Context, req *service.AttackLittleBossRequest, resp *service.AttackWorldBossResp) error {

	if time.Now().Hour() < LittleBossStarHour || time.Now().Hour() >= LittleBossStarHour+6 {
		return errors.ParamError
	}

	var boss, err = models.GetLittleBoss(database.DB, req.Role.RoleID)
	if err != nil {
		return err
	}

	if v := boss.IDsMap[req.CaptainID]; v == 1 || v == 3 {
		// 1已出战, 3复活再次已出战
		return errors.ParamError
	}

	var conf = configuration.GetLittleBoss(boss.Level)

	//单次伤害获得副本积分 = MAX ( 总副本积分 * 伤害比例 /( 伤害比例 + 9 ), 1)
	//单次伤害获得幽灵币 = MAX ( 总幽灵币 * 伤害比例 /( 伤害比例 + 9 ), 1)

	var rate = float64(req.Damage) / float64(conf.BossHP)
	var points = int32(math.Max(float64(conf.Score)*rate/(rate+9), 1))
	var itemNum = int32(math.Max(float64(conf.Coupon)*rate/(rate+9), 1))

	boss.HP -= req.Damage
	if boss.HP <= 0 {
		boss.HP = 0

		resp.Kill = true
		resp.Award = awardM.RandAward(req.Role, conf.KillAwardID)

		for _, v := range resp.Award.AwardItems {

			var msg = entity.BossMessage{
				ServerID: h.GetServerID(ctx),
				RoleID:   req.Role.RoleID,
				Name:     req.Role.Name,
				Lv:       boss.Level,
				ItemID:   v.RawItem.ItemID,
			}

			models.CreateBossMessage(&msg)
			_ = broker.RoomPublish("boss", h.GetServerID(ctx), pb.WsCode_CodeLBossMsg, buildPbBossMsg(msg))
			break
		}

		resp.Award = awardM.RandAward(req.Role, conf.AttackAwardID)
		resp.Award.AddRawItem(&pb.RawItem{ItemID: consts.WorldBoss, Number: itemNum})

		if boss.Level < 20 {

			boss.Level += 1
			boss.HP = configuration.GetLittleBoss(boss.Level).BossHP

			conf = configuration.GetLittleBoss(boss.Level)
		}
	}

	boss.IDsMap[req.CaptainID] = boss.IDsMap[req.CaptainID] + 1
	if err = models.SaveLittleBoss(database.DB, boss); err != nil {
		return err
	}

	resp.Boss = &pb.WorldBossReply{
		Boss:       &pb.WorldBoss{HP: boss.HP, MaxHP: conf.BossHP, IDs: boss.IDsMap},
		LittleBoss: &pb.LittleBoss{Level: boss.Level},
	}

	resp.Success = true
	resp.Points = points
	return nil
}

func (h *WorldBossServiceHandler) GetWorldBoss(_ context.Context, req *service.DefaultRequest, resp *pb.WorldBoss) error {

	var Points, err = models.GetPoints(database.DB, req.RoleID, req.Value)
	if err != nil {
		return err
	}

	var now = time.Now()

	if now.Hour() < WorldBossStarHour {

		var year, month, day = now.Date()
		resp.Second = int32(time.Date(year, month, day, WorldBossStarHour, 0, 0, 0, time.Local).Sub(now).Seconds())
	} else if now.Hour() > WorldBossStarHour {

		var year, month, day = now.AddDate(0, 0, 1).Date()
		resp.Second = int32(time.Date(year, month, day, WorldBossStarHour, 0, 0, 0, time.Local).Sub(now).Seconds())
	}

	var boss = h.getBoss(req.Value)

	resp.MaxHP = boss.MaxHP
	resp.HP = boss.GetHP()
	resp.IDs = Points.IDsMap
	resp.KillerName = boss.KillerName

	return nil
}

func (h *WorldBossServiceHandler) AttackWorldBoss(ctx context.Context, req *service.AttackWorldBossRequest, resp *service.AttackWorldBossResp) error {

	if time.Now().Hour() != WorldBossStarHour {
		return errors.ParamError
	}

	var serverID = h.GetServerID(ctx)
	var boss = h.getBoss(serverID)
	if boss.GetHP() <= 0 {
		resp.Success = false
		return nil
	}

	var bossPoints, err = models.GetPoints(database.DB, req.Role.RoleID, serverID)
	if err != nil {
		return err
	}

	if v := bossPoints.IDsMap[req.CaptainID]; v == 1 || v == 3 {
		// 1已出战, 3复活再次已出战
		return errors.ParamError
	}

	boss.AtkTime = time.Now()

	var conf = configuration.GetBoss()

	//单次伤害获得副本积分 = MAX ( 总副本积分 * 伤害比例 /( 伤害比例 + 9 ), 1)
	//单次伤害获得幽灵币 = MAX ( 总幽灵币 * 伤害比例 /( 伤害比例 + 9 ), 1)

	var rate = float64(req.Damage) / float64(boss.MaxHP)
	var points = int32(math.Max(float64(conf.Score)*rate/(rate+9), 1))
	var itemNum = math.Max(float64(conf.Coupon)*rate/(rate+9), 1)

	boss.Damages[req.Role.RoleID] = boss.Damages[req.Role.RoleID] + req.Damage

	var hp = boss.SubHP(req.Damage)
	if hp < 0 {

		if hp+req.Damage != boss.HP {
			// 并发操作 攻击失败
			return nil
		}

		bossPoints.Kill = int32(time.Now().Unix())

		boss.Killer = req.Role.RoleID
		boss.KillerName = req.Role.Name
		database.DB.Save(boss)

		resp.Kill = true
		resp.Award = awardM.RandAward(req.Role, conf.KillAwardID)

	} else {

		resp.Award = awardM.RandAward(req.Role, conf.AttackAwardID)
	}

	bossPoints.IDsMap[req.CaptainID] = bossPoints.IDsMap[req.CaptainID] + 1
	bossPoints.Points += points
	_ = models.SavePoints(database.DB, bossPoints)

	resp.Boss = &pb.WorldBossReply{
		Boss: &pb.WorldBoss{
			MaxHP: boss.MaxHP,
			HP:    boss.GetHP(),
			IDs:   bossPoints.IDsMap,
		},
	}

	resp.Points = points
	resp.Success = true
	resp.Award.AddRawItem(&pb.RawItem{ItemID: consts.WorldBoss, Number: int32(itemNum)})

	// daily quest
	questC.DailyQuestWorldBoss(req.Role.RoleID)
	return nil
}

type Points []*service.RankingPoints

func (c Points) Len() int           { return len(c) }
func (c Points) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
func (c Points) Less(i, j int) bool { return c[i].Points > c[j].Points }

func (h *WorldBossServiceHandler) DamageRanking(ctx context.Context, _ *empty.Empty, resp *service.DamageRankingResp) error {

	var boss = h.getBoss(h.GetServerID(ctx))

	var data []*service.RankingPoints
	for k, v := range boss.Damages {
		data = append(data, &service.RankingPoints{RoleID: k, Points: v})
	}

	sort.Sort(Points(data))

	resp.Ranking = make([]*service.RankingPoints, 0, 10)
	for k, v := range data {
		if k < 10 {
			resp.Ranking = append(resp.Ranking, v)
		}

		if v.RoleID == h.GetRoleID(ctx) {
			resp.Number = &pb.RankingNumber{
				Number: int32(k + 1),
				Points: v.Points,
			}
		}
	}

	return nil
}

func (h *WorldBossServiceHandler) Ranking(_ context.Context, req *wrappers.Int32Value, resp *service.RankingPointsList) error {

	var data []entity.BossPoints

	if err := database.DB.Where("server_id = ?", req.Value).Order("points DESC").Order("updated_at ASC").Limit(200).Find(&data).Error; err != nil {
		return err
	}

	resp.Ranking = make([]*service.RankingPoints, len(data))
	for k, v := range data {
		resp.Ranking[k] = &service.RankingPoints{
			RoleID: v.RoleID,
			Points: int64(v.Points),
		}
	}

	return nil
}

func (h *WorldBossServiceHandler) KillRanking(_ context.Context, req *wrappers.Int32Value, resp *service.RankingPointsList) error {

	var data []entity.BossPoints

	if err := database.DB.Where("server_id = ?", req.Value).Order("kill DESC").Order("updated_at ASC").Limit(200).Find(&data).Error; err != nil {
		return err
	}

	resp.Ranking = make([]*service.RankingPoints, len(data))
	for k, v := range data {
		resp.Ranking[k] = &service.RankingPoints{
			RoleID: v.RoleID,
			Points: int64(v.Points),
		}
	}

	return nil
}

func (h *WorldBossServiceHandler) MyRanking(ctx context.Context, _ *empty.Empty, resp *pb.RankingNumber) (err error) {

	var data = entity.BossPoints{
		RoleID: h.GetRoleID(ctx),
	}
	if err = database.DB.First(&data).Error; err != nil {
		return err
	}

	var number int32
	if err = database.DB.Table("boss_points").Where("server_id = ? AND points > ?", h.GetServerID(ctx), data.Points).Count(&number).Error; err != nil {
		return err
	}
	//
	//var number2 int32
	//if err = database.DB.Table("boss_points").Where("server_id = ?", req.Value).Where("points = ?", data.Points).Where("updated_at < ?", data.UpdatedAt).Count(&number2).Error; err != nil {
	//	return err
	//}

	resp.Points = int64(data.Points)
	resp.Number = number + 1
	return nil
}

func (h *WorldBossServiceHandler) MyKillRanking(ctx context.Context, _ *empty.Empty, resp *pb.RankingNumber) (err error) {

	var data = entity.BossPoints{
		RoleID: h.GetRoleID(ctx),
	}
	if err = database.DB.First(&data).Error; err != nil {
		return err
	}

	var number int32
	if err = database.DB.Table("boss_points").Where("server_id = ? AND kill > ?", h.GetServerID(ctx), data.Points).Count(&number).Error; err != nil {
		return err
	}

	//if err = database.DB.Table("boss_points").Where("server_id = ?", req.Value).Where("kill > ?", data.Points).Count(&number).Error; err != nil {
	//	return err
	//}
	//
	//var number2 int32
	//if err = database.DB.Table("boss_points").Where("server_id = ?", req.Value).Where("kill = ?", data.Points).Where("updated_at < ?", data.UpdatedAt).Count(&number2).Error; err != nil {
	//	return err
	//}

	resp.Points = int64(data.Kill)
	resp.Number = number + 1
	return nil
}

func (h *WorldBossServiceHandler) Resurrection(ctx context.Context, req *wrappers.Int32Value, resp *pb.WorldBoss) error {

	// 101051 海之号角
	if err := itemC.AsyncDelItem(h.GetRoleID(ctx), h.GetServerID(ctx), 101051, 1); err != nil {
		return err
	}

	if time.Now().Hour() >= LittleBossStarHour && time.Now().Hour() < LittleBossStarHour+6 {

		var boss, err = models.GetLittleBoss(database.DB, h.GetRoleID(ctx))
		if err != nil {
			return err
		}

		if v := boss.IDsMap[req.Value]; v == 1 {
			boss.IDsMap[req.Value] = 2
		}

		resp.HP = boss.HP
		resp.MaxHP = configuration.GetLittleBoss(boss.Level).BossHP
		resp.IDs = boss.IDsMap

		return models.SaveLittleBoss(database.DB, boss)

	} else if time.Now().Hour() == WorldBossStarHour {

		var boss = h.getBoss(h.GetServerID(ctx))

		var bossPoints, err = models.GetPoints(database.DB, h.GetRoleID(ctx), h.GetServerID(ctx))
		if err != nil {
			return err
		}

		if v := bossPoints.IDsMap[req.Value]; v == 1 {
			bossPoints.IDsMap[req.Value] = 2
		}

		resp.HP = boss.HP
		resp.MaxHP = boss.MaxHP
		resp.IDs = bossPoints.IDsMap

		return models.SavePoints(database.DB, bossPoints)
	}

	return errors.ParamError
}

func (h *WorldBossServiceHandler) LBossMessages(ctx context.Context, _ *empty.Empty, resp *pb.LBossMessages) error {

	var data []entity.BossMessage
	database.DB.Where("server_id = ?", h.GetServerID(ctx)).Order("id DESC").Limit(20).Find(&data)

	resp.Messages = make([]*pb.LBossMsg, 0, len(data))
	for _, v := range data {
		resp.Messages = append(resp.Messages, buildPbBossMsg(v))
	}

	return nil
}

func (h *WorldBossServiceHandler) getBoss(serverID int32) *entity.Boss {

	return h.Managers[serverID]
}

func work(dB *gorm.DB, boss *entity.Boss) {

	// 间隔时间2秒
	var second = time.Second * 2

	var ticker = time.NewTicker(second)
	for range ticker.C {

		if boss.HP < boss.MaxHP {
			setHP(dB, boss)
		}
	}
}

func setHP(dB *gorm.DB, boss *entity.Boss) {

	boss.Lock()
	defer boss.Unlock()

	var now = time.Now()

	if !boss.IsSave && now.Hour() >= WorldBossStarHour+1 {

		boss.IsSave = true

		// 重新计算血量
		//0~5分钟	+5%
		//	5~10分钟	+2%
		//	10~20分钟	+0%
		//	20~30分钟	-2%
		//	30~60分钟	-5%
		//	未击杀	-10%

		if boss.HP > 0 {
			boss.MaxHP = boss.MaxHP / 100 * 90
		} else {
			var year, month, day = now.Date()
			var nanosecond = boss.AtkTime.Sub(time.Date(year, month, day, WorldBossStarHour, 0, 0, 0, time.Local))

			if nanosecond <= time.Minute*5 {

				boss.MaxHP = boss.MaxHP / 100 * 105
			} else if nanosecond <= time.Minute*10 {

				boss.MaxHP = boss.MaxHP / 100 * 102
			} else if nanosecond <= time.Minute*20 {

			} else if nanosecond <= time.Minute*30 {

				boss.MaxHP = boss.MaxHP / 100 * 98
			} else if nanosecond <= time.Minute*60 {

				boss.MaxHP = boss.MaxHP / 100 * 95
			}
		}

		var initHP = configuration.GetBoss().BossHP
		if boss.MaxHP < initHP {
			boss.MaxHP = initHP
		}

		boss.HP = boss.MaxHP
		boss.PublishHP = boss.HP
		dB.Save(boss)

	} else {
		// 推送HP
		if time.Now().Sub(boss.AtkTime) < time.Second*2 && boss.HP < boss.PublishHP {
			boss.PublishHP = boss.HP

			_ = broker.RoomPublish("boss", boss.ServerID, pb.WsCode_WorldBossHP, &wrappers.Int64Value{Value: boss.HP})
		}
	}
}

func buildPbBossMsg(data entity.BossMessage) *pb.LBossMsg {
	return &pb.LBossMsg{
		Name:      data.Name,
		Level:     data.Lv,
		ItemID:    data.ItemID,
		Timestamp: data.CreatedAt.Unix(),
	}
}
